home *** CD-ROM | disk | FTP | other *** search
/ Amiga Developer CD 2.1 / Amiga Developer CD v2.1.iso / Reference / DevCon / Washington_1988 / DevCon88.2 / Audio / release.3.early / audiotools3.c next >
Encoding:
C/C++ Source or Header  |  1992-08-27  |  36.2 KB  |  1,206 lines

  1. /* audiotools2.c */
  2.  
  3. /* audiotools.h built in here so that audiotools is a package deal */
  4.  
  5. #define StartChannel(c) ControlChannel(c, CMD_START)
  6. #define StopChannel(c)  ControlChannel(c, CMD_STOP)
  7. #define ResetChannel(c) ControlChannel(c, CMD_RESET)
  8. #define FlushChannel(c) ControlChannel(c, CMD_FLUSH)
  9.  
  10. #define  BIG_WAVE          256L   /* size of biggest waveform */
  11. #define  NBR_WAVES         7L     /* number of waves per instrument */
  12. #define  WAVES_TOTAL       1024L  /* alloc size for instrument's waves */
  13.  
  14. #define  DEFAULT_DURATION  500L      /* 500/1000ths of a second default */
  15. #define  AUDBUFFERS        20L      /* iob msg packets before need to allot */
  16. #define  YES               1L
  17. #define  NO                0L
  18.  
  19. #define  BAD_CHANNEL_SELECTED -1L /* channel # out of range */
  20. #define  NOT_YOUR_CHANNEL     -2L /* not owned by your task */
  21. #define  OUT_OF_RANGE_FREQ    -3L /* frequency that we cannot play */
  22. #define     OUT_OF_RANGE_PRI     -4L /* priority value wont fit in 1 byte */
  23.  
  24. /* REDEFINITION from article - now contains one new field at the bottom
  25.  * of the data structure.
  26.  */
  27. struct ExtIOB {
  28.     struct     IORequest ioa_Request;
  29.     WORD    ioa_AllocKey;
  30.     UBYTE    *ioa_Data;
  31.     ULONG    ioa_Length;
  32.     UWORD    ioa_Period;
  33.     UWORD    ioa_Volume;
  34.     UWORD    ioa_Cycles;
  35.     struct    Message ioa_WriteMsg;    /* up to here, same as IOAudio */
  36.     LONG    iob_Identifier;        /* This field is added */
  37. };
  38.  
  39. /* a few forward declarations */
  40.  
  41. extern struct ExtIOB     *GetIOB();
  42. extern int         FreeIOB();
  43. extern int         GetChannel();
  44. extern int         GetStereoPair();
  45. extern int         InitBlock();
  46. extern struct MsgPort     *CreatePort();
  47.  
  48. extern APTR         AllocMem();
  49. extern struct Message     *GetMsg();
  50. extern struct Task    *FindTask();
  51.  
  52. struct auMsg {
  53.    struct Message au_Message;
  54.    LONG aum_Identifier;   /* matches the bottom of ExtIOB */
  55. };
  56.  
  57. /* Note that the definition of ExtIOB has now been changed (added to) */
  58.  
  59. /* forward declaration */
  60. extern struct MsgPort *InitAudio();
  61.  
  62. /* these are used to keep track of allocated channels */
  63.  
  64. struct Unit    *unit[4];    /* global pointers to Units        */
  65. WORD         key[4];        /* global value for alloc keys     */
  66. struct Task     *usertask[4];    /* in preparation for making this
  67.                  * a shared library of routines
  68.                  * (loadable from disk), keep track
  69.                  * of which user actually owns a
  70.                  * channel currently.
  71.                  */
  72.  
  73. struct  IOAudio     openIOB;      /* IOB to open and close the device */
  74. struct  MsgPort     *auReplyPort; /* temporary pointer */
  75. struct  MsgPort     *controlPort; /* Port for ControlChannel functions */
  76.  
  77. char    *globalname     = "global";    /* the name for global IOB's  */
  78. char    *dynamicname     = "dynamic";    /* the name for dynamic IOB's */
  79.  
  80. UBYTE stereostuff[4]     = { 3, 5, 10, 12 };    /* channel masks for stereo */
  81. UBYTE anychan[4]     = { 1, 2, 4, 8 };    /* channel masks for mono */
  82.  
  83. /* Resolve most all externals */
  84.  
  85. struct ExtIOB          audbuffer[AUDBUFFERS];    /* globals to build-in       */
  86. UBYTE             *chipaudio[4];    /* pointers to waveforms in CHIP RAM */
  87. struct Device         *device;    /* global pointer to audio device  */
  88. LONG             datalength[4];    /* length of the data for a wave   */
  89. struct MsgPort         *replyPort[4];  /* one ReplyPort per channel       */
  90. BYTE             inuse[AUDBUFFERS]; /* keep track of globals in-use */
  91. LONG            dynamix[4];    /* counters for how many
  92.                      * dynamically allocated audio
  93.                      * message I/O blocks 
  94.                      */
  95.  
  96.  
  97. /* Each waveform buffer contains 8 octaves of the wave.  
  98.  * The offset values specify where in the buffer the
  99.  * proper waveform table for that octave begins.
  100.  */
  101. int woffsets[]     = { 0, 256, 384, 448, 480, 496, 504, 508, 510 };
  102.  
  103. /* Length of each waveform within a buffer */
  104. int wlen[]     = { 256, 128, 64, 32, 16, 8, 4, 2, 1 };
  105.  
  106. /* Period value to go with particular notes within an octave. */
  107.  
  108. int perval[]     = { 428, 404, 381, 360, 339, 320, 
  109.                  302, 285, 269, 254, 240, 226, 214 };
  110.  
  111. UBYTE *w1, *w2, *w3;
  112. BYTE *owptr[4]     = { NULL, NULL, NULL, NULL };
  113.  
  114. /* InitAudio is different from that published in the article -
  115.  
  116. InitAudio now returns a value, an address of a message port at which
  117. your task receives a message when a particular note BEGINS to play.
  118. You must save this value somewhere, and use it to call MayGetNote
  119. or FinishAudio.  MayGetNote is the name of the routine that you call
  120. to check if a note has begun to play.
  121.  
  122. InitAudio also has been modified to return to its caller when there
  123. are problems, rather than calling finishup routines itself.  This is better
  124. programming practice.
  125.  
  126. */
  127.  
  128. struct MsgPort *    
  129. InitAudio()
  130. {
  131.       LONG error,i;
  132.       struct MsgPort *userport;    /* NEW item */
  133.  
  134.       LONG firstuser;    /* THIS WILL GET MOVED when shared library is made */
  135.       firstuser = TRUE;
  136.  
  137.       /* Declare all message blocks available */
  138.       for(i=0; i<AUDBUFFERS; i++)  {   inuse[i] = NO;   }
  139.  
  140.       /* Open device but don't allocate channels     */
  141.       openIOB.ioa_Length = 0;   /* (no allocation table) */
  142.  
  143.       error = OpenDevice("audio.device",0,&openIOB,0);
  144.       if(error) return((struct MsgPort *)0);
  145.  
  146.       /* Get the device address for later use */
  147.       device = openIOB.ioa_Request.io_Device;
  148.    
  149.    /* Create ports for replies from each channel as well as
  150.     * one port to be used for the control and synchonous functions */
  151.  
  152.    for(i=0; i<4; i++) 
  153.    {   
  154.       auReplyPort = CreatePort(0,0);
  155.       replyPort[i] = auReplyPort;
  156.       if(auReplyPort == 0) return((struct MsgPort *)0);
  157.       chipaudio[i] = 0;  /* have not yet created the waves */
  158.  
  159.       datalength[i] = 1; /* might use for custom sound samples  */
  160.  
  161.       /* Also, zero out key values for each channel, as well as
  162.        * unit value and usertask value (no channel owned by any task)
  163.        */
  164.  
  165.     /* When implemented as a shared library, "firstuser" will only 
  166.       * be true when the library is first opened.
  167.       */
  168.       if(firstuser)    
  169.       {
  170.     key[i]  = 0;
  171.     unit[i] = 0;
  172.     usertask[i] = 0;
  173.       }
  174.    }
  175.    controlPort = CreatePort(0,0);
  176.    if(controlPort == 0) return((struct MsgPort *)0);
  177.  
  178.    error = MakeWaves();
  179.    if(error == -1) return((struct MsgPort *)0);
  180.  
  181.    for(i=0; i<4; i++)
  182.    { dynamix[i] = 0; }   /* no dynamic I/O blocks allocated 
  183.                       * for any channel thus far */
  184.  
  185.    userport = CreatePort(0,0);
  186.  
  187. return(userport);
  188. }
  189.  
  190. int 
  191. CheckIOBDone()
  192. {
  193.    LONG i, status;
  194.  
  195.    status = 0;   /* means there are still some iob's in play */
  196.          /* when status = 5, then everything is free */
  197.  
  198.    for(i=0; i<AUDBUFFERS; i++)
  199.    {   if(inuse[i] == YES)
  200.        {   
  201.      /* Sooner or later, this will catch both
  202.           * the statics and dynamics.  Note that
  203.           * this will only work if NO (REPEAT: NO)
  204.           * iob's sent off with a duration value
  205.           * of "0", because zero means "forever"
  206.           */
  207.          ReEmployIOB();
  208.       }
  209.    }
  210.    /* Note to implementors... maintaining inuse[i] now seems
  211.     * like a lousy idea, unless it is accompanied by a variable
  212.     * statics_inplay that decrements to zero when all statics
  213.     * are done.  That makes it much easier to check than going
  214.     * through all of the inuse[]'s.
  215.     */
  216.  
  217.    for(i=0; i<4; i++)
  218.    {   
  219.       if(dynamix[i] > 0)  
  220.             /* If this channel still playing a   */
  221.                  /* dynamically allocated block, wait */
  222.                  /* for all messages to return before */
  223.                  /* the program exits.                */
  224.       {
  225.              ReEmployIOB();  /* take another shot at freeing it all */  
  226.       }
  227.    }
  228.    for(i=0; i<4; i++)   /* Check again as we nearly exit */
  229.    {
  230.       if(dynamix[i] == 0) status++;
  231.    }
  232.    if(status == 4)      /* All dynamics are free, now check
  233.              * the statics.  Any not free force
  234.              * an early return. */
  235.    {
  236. #ifdef DEBUG
  237.       printf("ch0,1,2,3 have %ld,%ld,%ld,%ld dyn.blocks playing\n",
  238.          dynamix[0], dynamix[1], dynamix[2], dynamix[3]);
  239. #endif DEBUG
  240.       for(i=0; i<AUDBUFFERS; i++)
  241.       {
  242.          if(inuse[i] == YES)
  243.          {
  244. #ifdef DEBUG
  245.          printf("iob still in use is: %ld\n",i);
  246. #endif DEBUG
  247.          return(0);
  248.          }
  249.       }
  250.       printf("All global I/O blocks are done\n");
  251.  
  252.       return(1);   /* DONE! */
  253.    }
  254.    else
  255.    {
  256.       return(0);   /* still some out there! */
  257.    }
  258. }
  259.  
  260.  
  261. FinishAudio(uport)
  262. struct MsgPort *uport;
  263. {
  264.    LONG i;
  265.    struct auMsg *aum;        /* A little bigger than a standard
  266.                      * message, but this routine will
  267.                      * not really know (or care) about
  268.                      * the difference.
  269.                      */
  270.    if(uport == 0)
  271.    {
  272.     goto no_init;        /* InitAudio didn't work, bypass
  273.                  * the usual port emptying and so on.
  274.                  */
  275.    }
  276.    /* If the user says FinishAudio, IT MEANS FINISH AUDIO.
  277.     * Flush anything that is still in play, NOW.  You can
  278.     * use "CheckIOBDone()" to see if everything is finished
  279.     * BEFORE you call FinishAudio.  If CheckIOBDone() is
  280.     * equal to zero (FALSE), it means that something is still
  281.     * playing.
  282.     */
  283.    for(i=0; i<4; i++)   FlushChannel(i);
  284.  
  285.    while(CheckIOBDone() == 0)
  286.    {
  287.       Delay(12);   /* Be a good multitasking neighbor;
  288.             * sleep a little before trying again */
  289.    }
  290.    /* Empty the port if the user has not yet done so */
  291.    while((aum = (struct auMsg *)GetMsg(uport)) != NULL)
  292.    {
  293.       aum->au_Message.mn_ReplyPort = 0;   /* let system deallocate it */
  294.    }
  295.    ReEmployIOB();   /* free all static and dynamic messages */
  296.  
  297.    for(i=0; i<4; i++)   FreeChannel(i);
  298.  
  299.    DeletePort(uport);
  300.  
  301. no_init:
  302.    if(device) CloseDevice(&openIOB);
  303.    printf("closed the device\n");
  304.  
  305.    for(i=0; i<4; i++)
  306.    {
  307.     if(chipaudio[i]) FreeMem(chipaudio[i],WAVES_TOTAL);
  308.     if(replyPort[i]) 
  309.     DeletePort(replyPort[i]);
  310.    }
  311.    if(controlPort) DeletePort(controlPort);
  312.    return(0);      /* no errors */
  313. }
  314.  
  315.  
  316. int
  317. ControlChannel(channel, command)
  318.    WORD channel;
  319.    WORD command;
  320. {
  321.    LONG rtn;
  322.    struct ExtIOB *iob, controlIOB;
  323.  
  324.    if(channel < 0 || channel > 3)    return(BAD_CHANNEL_SELECTED);
  325.    if(usertask[channel] != FindTask(0))    return(NOT_YOUR_CHANNEL);
  326.  
  327.    iob = &controlIOB;
  328.    iob->ioa_Request.io_Device    = device;
  329.    iob->ioa_Request.io_Message.mn_ReplyPort = controlPort;
  330.  
  331.    InitBlock(iob,channel);   /* init it for CMD_WRITE, then change */
  332.  
  333.    iob->ioa_Request.io_Command = command;
  334.    iob->ioa_Request.io_Flags   = IOF_QUICK;
  335.  
  336.    BeginIO(iob);
  337.    WaitIO(iob);
  338.    rtn = ((LONG)(iob->ioa_Request.io_Error));
  339.    return(rtn);
  340. }
  341.  
  342. struct ExtIOB *
  343. GetIOB(ch)
  344.    LONG ch;
  345. {
  346.    WORD i,use_reply;
  347.    struct ExtIOB *iob;  /* in case we need to allocate one */
  348.    ReEmployIOB();    /* find already used ones and free them */
  349.               /* so that when we do a get... */
  350.    if(ch == -1)  use_reply = 0;  /* which reply port to use */
  351.    else          use_reply = ch;
  352.  
  353.    for(i=0; i<AUDBUFFERS; i++)
  354.    {   
  355.     if(inuse[i] == NO)
  356.            {   
  357.         inuse[i] = YES;
  358.  
  359.         audbuffer[i].ioa_Request.io_Device    = device;
  360.         audbuffer[i].ioa_Request.io_Message.mn_ReplyPort = 
  361.                                   replyPort[use_reply];
  362.         audbuffer[i].ioa_Request.io_Message.mn_Length = i;
  363.         audbuffer[i].ioa_Request.io_Message.mn_Node.ln_Name = 
  364.                                   globalname;
  365. #ifdef DEBUG
  366.       printf("Using global iob\n");
  367. #endif DEBUG
  368.       return(&audbuffer[i]);
  369.        }
  370.    }
  371.    /* if all globals are in use, have to allocate one */
  372.    iob = (struct ExtIOB *)AllocMem(sizeof(struct ExtIOB),
  373.                      MEMF_CLEAR );
  374.    /* FIXED A BUG HERE, THIS ^^^^^^ was MEMF_FAST */
  375.  
  376.    if(iob == 0) return(0);   /* out of memory */
  377.    else
  378.    {   
  379.     iob->ioa_Request.io_Device = device;
  380.           iob->ioa_Request.io_Message.mn_ReplyPort = 
  381.                replyPort[use_reply];
  382.           iob->ioa_Request.io_Message.mn_Node.ln_Name = 
  383.                dynamicname;
  384.           iob->ioa_Request.io_Message.mn_Length = dynamix[use_reply];
  385.           dynamix[use_reply] += 1; /* add one to number allocated
  386.                           * for a specific channel */
  387. #ifdef DEBUG
  388.           printf("Using dynamic iob\n");
  389. #endif DEBUG
  390.           return(iob);
  391.     }
  392. return(0);
  393. }
  394.  
  395.  
  396. /* Free a global or an allocated IOB */
  397. int
  398. FreeIOB(iob, ch)
  399.    struct ExtIOB *iob;
  400.    LONG ch;   /* which channel was it attached to? */
  401. {
  402.    WORD i;
  403.  
  404.    if(iob->ioa_Request.io_Message.mn_Node.ln_Name == dynamicname)
  405.    {   
  406.     FreeMem(iob, sizeof(struct ExtIOB));
  407.           if(dynamix[ch]) dynamix[ch] -= 1; /* subtract one if nonzero */
  408.           return(0L);
  409.    }
  410.    else if(iob->ioa_Request.io_Message.mn_Node.ln_Name == globalname)
  411.    {       
  412.     i = iob->ioa_Request.io_Message.mn_Length;
  413.  
  414.           if(i < AUDBUFFERS)
  415.           {   
  416.         inuse[i] = NO;   /* frees this one for reuse */
  417.           }
  418.           return(0L);
  419.    }
  420.    /* if get here, the names don't match... something is wrong.*/
  421.    else 
  422.    {   
  423.         printf("FreeIOB: names don't match...unknown error\n");
  424.            return(-1);   /* unknown source of IOB fed to routine. */
  425.    }
  426. return(0);
  427. }
  428.  
  429. /* Initialize an audio I/O block for default CMD_WRITE operation. */
  430. int
  431. InitBlock(iob, channel)
  432.      struct ExtIOB *iob;
  433.    WORD channel;
  434. {
  435.    /* Device and ReplyPort fields have been initialized by GetIOB */
  436.    iob->ioa_Request.io_Unit = unit[channel];
  437.  
  438.    /* Allocation key */
  439.    iob->ioa_AllocKey = key[channel];
  440.  
  441.    /* Where is the waveform?  Just be sure is in MEMF_CHIP!!! */
  442.  
  443.    /* USER initializes datalength[ch] before calling this;    */
  444.    /* for sampled sound command write operation.              */
  445.    iob->ioa_Data    = chipaudio[channel];
  446.    iob->ioa_Length = datalength[channel];
  447.  
  448.    /* Another routine, must initialize:
  449.  
  450.       period      ioa_Period
  451.       volume      ioa_Volume
  452.       cycles      ioa_Cycles
  453.       message      ioa_WriteMsg
  454.    */
  455.    /* Default command type is CMD_WRITE */
  456.    iob->ioa_Request.io_Command = CMD_WRITE;
  457.  
  458.    /* If IOF_QUICK is zeroed, this would affect the
  459.     * period and volume.  If a CMD_WRITE, it queues if
  460.     * another note is already playing.  We queue CMD_WRITES.
  461.     */
  462.    iob->ioa_Request.io_Flags = ADIOF_PERVOL;
  463.    return(0);
  464. }
  465.  
  466.  
  467. /* To request "any" channel, use ch = -1;
  468.  * To request a specific channel, use ch = {0, 1, 2 or 3};
  469.  * Again NOTE, this returns two globals as well as the channel number!
  470.  */
  471.  
  472. int
  473. GetChannel(ch)
  474.    LONG ch;
  475. {
  476.    int error, value;
  477.    struct ExtIOB *iob, controlIOB;
  478.  
  479.    iob = &controlIOB;
  480.    iob->ioa_Request.io_Device    = device;
  481.    iob->ioa_Request.io_Message.mn_ReplyPort = controlPort;
  482.  
  483.    InitBlock(iob,0);   /* init it for CMD_WRITE, then change */
  484.  
  485.    iob->ioa_Request.io_Message.mn_Node.ln_Pri = 20;
  486.    iob->ioa_Request.io_Command = ADCMD_ALLOCATE;
  487.  
  488.    if(ch == -1)
  489.    {   
  490.     iob->ioa_Data = (UBYTE *)anychan;
  491.           iob->ioa_Length = 4;
  492.    }
  493.    else if(ch >=0 && ch <= 3)
  494.    {       
  495.     /* Test to be sure that this channel is now free.  If
  496.      * usertask[i] is not zero, either the current task 
  497.      * has already allocated this channel, or a different
  498.      * task is now using it. 
  499.      */
  500.  
  501.     /* NOTE ***** ENHANCEMENT COMING HERE ***** */
  502.  
  503.     if(usertask[ch] != 0) return(-1);
  504.  
  505.     /* Enhancement might be: look at the running priority
  506.       * of the current task as compared to the running priority
  507.      * of the task in usertask[i].  If not same task and if
  508.      * the current task has a higher priority, STEAL the channel!
  509.      * Alternative (seems better) is to have a global variable
  510.      * called audPriority to be set by a new function SetAudPriority
  511.      * (for a given task only), and that global priority value
  512.      * would be used for GetChannel and LockChannel requests.
  513.      */
  514.     iob->ioa_Data = (UBYTE *)(&anychan[ch]);
  515.           iob->ioa_Length = 1;
  516.    }
  517.    else   /* chose a bad channel number; cannot allocate it */
  518.    {   
  519.     return(-1);
  520.    }
  521.    iob->ioa_Request.io_Flags = ADIOF_NOWAIT | IOF_QUICK;
  522.    BeginIO(iob); 
  523.    error = WaitIO(iob);  /* returns nonzero if error */
  524.    if(!(iob->ioa_Request.io_Flags & IOF_QUICK))
  525.    {   
  526.     GetMsg(iob->ioa_Request.io_Message.mn_ReplyPort);
  527.    }
  528.    if(error)
  529.    {   
  530.     return(-1);
  531.    }
  532.    switch((LONG)(iob->ioa_Request.io_Unit))
  533.    {   
  534.     case  1:   value = 0;   break;
  535.           case  2:   value = 1;   break;
  536.           case  4:   value = 2;   break;
  537.           case  8:   value = 3;   break;
  538.           default:   value = -1;  break;
  539.    }
  540.    if(value == -1) return(-1L);
  541.  
  542.    unit[value]     = (iob->ioa_Request.io_Unit);
  543.    key[value]      = (iob->ioa_AllocKey);
  544.    usertask[value] = FindTask(0);    /* THIS user task owns it now */
  545.  
  546.    return(value);
  547. }
  548.  
  549. int
  550. FreeChannel(channel)
  551.    LONG channel;
  552. {
  553.    int error;
  554.    struct ExtIOB *iob, controlIOB;
  555.  
  556.    if(channel < 0 || channel > 3)    return(BAD_CHANNEL_SELECTED);
  557.    if(usertask[channel] != FindTask(0))    return(NOT_YOUR_CHANNEL);
  558.  
  559.    iob = &controlIOB;
  560.    iob->ioa_Request.io_Device    = device;
  561.    iob->ioa_Request.io_Message.mn_ReplyPort = controlPort;
  562.  
  563.    InitBlock(iob,channel);       /* init it for CMD_WRITE, then change it */
  564.                     /* (pick up unit, key value for channel) */
  565.    iob->ioa_Request.io_Command = ADCMD_FREE;
  566.    iob->ioa_Request.io_Flags = ADIOF_NOWAIT | IOF_QUICK;
  567.    BeginIO(iob); 
  568.    error = WaitIO(iob);  /* returns nonzero if error */
  569.  
  570.    /* Educational note - the docs indicate that audio, even though 
  571.     * told to do an io item as "quickio", can, if it chooses, not do
  572.     * it quick.  In that case, the reply is queued to the
  573.     * reply port and it has to be removed.  That is why
  574.     * the following status check has been installed here.
  575.     * (Actually this check probably can be removed because
  576.     * WaitIO, just above, removes the message from the port
  577.     * if it does arrive there after all.)
  578.     */
  579.    if(!(iob->ioa_Request.io_Flags & IOF_QUICK))
  580.    {       
  581.     GetMsg(iob->ioa_Request.io_Message.mn_ReplyPort);
  582.    }
  583.    usertask[channel] = 0;    /* free again... */
  584.    if(error)
  585.    {   
  586.     return(error);
  587.    }
  588.    return(0);
  589. }
  590.  
  591. /* SOME OF THE FOLLOWING ROUTINES ARE PARAPHRASED FROM A USENET and BIX
  592.  * POSTING MADE IN 1985 BY STEVEN A. BENNETT.  
  593.  */
  594. /* I have modified his routines to queue the audio commands in 
  595.  * place of starting forever-duration and canceling each note.
  596.  * Many of his original comments have been incorporated into
  597.  * the article. 
  598.  */
  599.  
  600. /* PlayNote(...) */
  601.  
  602. /* ******************************************************************* */
  603. /* NOTE: There are some differences in PlayNote as compared to the article.
  604.  * See the audiotools.DOC for details.
  605.  */
  606. /* ******************************************************************* */
  607.  
  608. /* Starts a sound on the channel with specified period and volume. */
  609. /* This nice little routine takes a note and plays it on the given
  610.  * voice.  The note is basically an integer from
  611.  * 0 to 11 (c to b) plus 12 per octave above the first and lowest. 
  612.  *
  613.  * The waveform to use is determined by adding an index (woffsets[]) 
  614.  * dependant on the octave.
  615.  *
  616.  * The length of the waveform (in wlen[]) is likewise dependant on
  617.  * the octave.  Note that octaves start with zero, not one.
  618.  */
  619. int 
  620. PlayNote(channel, note, wf, vol, duration, priority, messageport, id)
  621.    char *wf;   /* waveform to use */
  622.    LONG vol, channel, duration, note;   /* specific note number */
  623.    LONG priority;
  624.    struct MsgPort *messageport;
  625.    LONG id;
  626. {
  627.    LONG per, len, oct;   /* period, length of waveform, which octave */
  628.    char *wavepointer;   /* where to find start of waveform */
  629.    struct ExtIOB *iob;
  630.    int frequency;
  631.  
  632.    if(channel < 0 || channel > 3)    return(BAD_CHANNEL_SELECTED);
  633.    if(usertask[channel] != FindTask(0))    return(NOT_YOUR_CHANNEL);
  634.    if(note < 0 || note > 95)         return(OUT_OF_RANGE_FREQ);
  635.    if(priority < -128 || priority > 127)return(OUT_OF_RANGE_PRI);
  636.  
  637.    iob = GetIOB(channel);
  638.  
  639.    if(iob != 0)
  640.    {
  641.     InitBlock(iob, channel);   /* set up for CMD_WRITE */
  642.    
  643.     oct = note / 12;
  644.        wavepointer = wf + woffsets[oct];
  645.        len = wlen[oct];
  646.        per = perval[note % 12];
  647.  
  648.        /* Set the parameters */
  649.        iob->ioa_Data = (UBYTE *)wavepointer;
  650.        iob->ioa_Length = len;
  651.        iob->ioa_Period = per;
  652.        iob->ioa_Volume = vol;
  653.  
  654.     iob->ioa_Request.io_Message.mn_Node.ln_Pri = priority;
  655.  
  656.        /* additions for support of tell-me-when-note-starts */
  657.  
  658.           iob->iob_Identifier = id;
  659.  
  660.     /* Initialize message port.  If 0, then no pushing back
  661.      * of a message.  If nonzero, message gets recirculated
  662.      * by ReEmployIOB until the user finally acknowledges it
  663.      * by using MayGetNote.
  664.       */
  665.     iob->ioa_WriteMsg.mn_ReplyPort = messageport;
  666.  
  667.     if(messageport != 0)
  668.        {
  669.               /* If 0, no sending message when note plays;
  670.                 * if nonzero, user gets a message that can
  671.              * be read by MayGetNote.  uport, received from 
  672.          * InitAudio, is where the message goes */
  673.  
  674.         /* Tell the audio device to "reply" to this message */
  675.         iob->ioa_Request.io_Flags |= ADIOF_WRITEMESSAGE;
  676.         }
  677.  
  678.    /* Look at the frequency that it is to play by backwards calc. */
  679.    frequency = 3579545 / (len * per);
  680.  
  681.    /* Calculate cycles from duration in 1000ths of a second */
  682.    /* Multiply all-in-one to maintain max precision possible */
  683.    /* (all integer arithmetic.) */
  684.  
  685.    iob->ioa_Cycles = ((LONG)(frequency * duration)/1000);
  686.    BeginIO(iob);
  687.    return(0);      /* all went ok */
  688.    }
  689. return(-1);    /* (else-part) iob was zero, couldn't do the above. */
  690. }
  691.  
  692.   
  693. /* SetPV(channel, per, vol)
  694.  *   int channel, per, vol;
  695.  */
  696. int 
  697. SetPV(channel, per, vol)
  698.    int channel, per, vol;
  699.    {
  700.    int error;
  701.    struct ExtIOB *iob, controlIOB;
  702.  
  703.    if(channel < 0 || channel > 3)    return(BAD_CHANNEL_SELECTED);
  704.    if(usertask[channel] != FindTask(0))    return(NOT_YOUR_CHANNEL);
  705.  
  706.    iob = &controlIOB;
  707.    iob->ioa_Request.io_Device    = device;
  708.    iob->ioa_Request.io_Message.mn_ReplyPort = controlPort;
  709.  
  710.    InitBlock(iob, channel);   /* set up for CMD_WRITE */
  711.    
  712.    iob->ioa_Period = per;
  713.    iob->ioa_Volume = vol;
  714.    iob->ioa_Request.io_Command = ADCMD_PERVOL;
  715.    iob->ioa_Request.io_Flags   = IOF_QUICK | ADIOF_PERVOL;
  716.    BeginIO(iob);   /* This one will be synchronous; affects whatever
  717.           * is playing on this channel at this time.
  718.           */
  719.    error = WaitIO(iob);   /* OK to wait, since it will return */
  720.    return(error);      /* copy of io_Error field; should be 0 */
  721. }
  722.  
  723. /* SetWaves(w1, w2, w3): create first sawtooth, triangle and square wave */
  724.  
  725. SetWaves(w1, w2, w3)
  726.    UBYTE *w1, *w2, *w3;
  727. {
  728.    int i, increment, value, sqvalue;
  729.    value = 0; increment = 2;
  730.    sqvalue = 127;
  731.  
  732.    for (i = 0; i < BIG_WAVE; ++i)
  733.    {
  734.    w1[i] = i;   /* do the sawtooth */
  735.  
  736.    if(i > 62 && i < 180) increment = -2;
  737.    else
  738.    if(i >= 180) increment = 2;
  739.  
  740.    w2[i] = value;  value += increment;  /* triangle wave */
  741.  
  742.    if(i > 126) sqvalue = -127;
  743.  
  744.    w3[i] = sqvalue;
  745.    }
  746. return(0);
  747. }
  748.   
  749. /* ExpandWave(wfp) - replicate waves in decreasing sample sizes
  750.  *   BYTE *wfp;
  751.  */
  752.  
  753. ExpandWave(wfp)
  754.    BYTE *wfp;
  755.    {
  756.    int i, j, rate;
  757.    BYTE *tptr;
  758.  
  759.    rate = 1;
  760.    tptr = wfp + BIG_WAVE;
  761.    for (i = 0; i < NBR_WAVES - 1; ++i)
  762.       {
  763.       rate *= 2;
  764.       for (j = 0; j < BIG_WAVE; j += rate)
  765.          *tptr++ = wfp[j];
  766.       }
  767.    return(0);
  768.    }
  769.   
  770. /* MakeWaves()
  771.  *
  772.  *   Just makes a sawtooth, triangle and square wave in chip mem 
  773.  * and expands them.
  774.  */
  775. int 
  776. MakeWaves()
  777. {
  778.    /* allocate the memory for the waveforms.
  779.     */
  780.    w1 = (UBYTE *)AllocMem(WAVES_TOTAL, MEMF_CHIP);
  781.    w2 = (UBYTE *)AllocMem(WAVES_TOTAL, MEMF_CHIP);
  782.    w3 = (UBYTE *)AllocMem(WAVES_TOTAL, MEMF_CHIP);
  783.  
  784.    if (w1 == NULL || w2 == NULL || w3 == NULL)
  785.    return(-1);   /* ran out of memory! */
  786.  
  787.    /* get and expand the waveforms    */
  788.  
  789.    SetWaves(w1, w2, w3);
  790.    ExpandWave(w1);   chipaudio[0]=w1;
  791.    ExpandWave(w2);   chipaudio[1]=w2;
  792.    ExpandWave(w3);   chipaudio[2]=w3;
  793.    return(0);
  794. }
  795.  
  796. /* =================================================================== */
  797. /* Here are NEW routines that did not appear in the AmigaWorld article.
  798.  * These are in addition to the changes noted above for InitAudio and
  799.  * FinishAudio, and changes in main() to accommodate them.
  800.  */
  801.  
  802. /* PlayFreq in this version is for INTEGER values of frequency only.
  803.  *
  804.  * To be more precise, you'll have to convert the integer calculations
  805.  * to floating point.   Almost identical to PlayNote, however, had to
  806.  * add some error checking to the parameters AHEAD OF GetIOB because
  807.  * if the frequency is out of range of what we have in our wave tables
  808.  * currently, we have to reject the command.  If it got rejected
  809.  * after Getting an IOB, then we'd have to free it.
  810.  */
  811. int 
  812. PlayFreq(channel, freq, wf, vol, duration, priority, messageport, id)
  813.    char *wf;   /* waveform to use */
  814.    LONG vol, channel, duration, freq;   /* specific integer frequency */
  815.    LONG priority;
  816.    struct MsgPort *messageport;
  817.    LONG id;
  818. {
  819.    LONG per, len, oct;   /* period, length of waveform, which octave */
  820.    char *wavepointer;   /* where to find start of waveform */
  821.    struct ExtIOB *iob;
  822.    LONG i, isave, accept;
  823.  
  824.    if(channel < 0 || channel > 3)    return(BAD_CHANNEL_SELECTED);
  825.    if(usertask[channel] != FindTask(0)) return(NOT_YOUR_CHANNEL);
  826.    if(priority < -128 || priority > 127)return(OUT_OF_RANGE_PRI);
  827.  
  828.    /* see if we CAN represent this frequency, if not, reject it */
  829.  
  830.    for(i=0; i<9; i++)
  831.    {
  832.        /* Now figure out which waveform to use...
  833.             * start with the first wlen value because
  834.             * we want to use the longest waveform we can.
  835.             */
  836.        isave = i;   
  837.  
  838.        accept = FALSE;
  839.  
  840.        per = 3579545 / (freq * wlen[i]);
  841.  
  842.        if(per > 127 && per < 500)
  843.        {
  844.               accept = TRUE;
  845.               break;
  846.        }
  847.    }
  848.    if(accept != TRUE)             return(OUT_OF_RANGE_FREQ);
  849.  
  850.    iob = GetIOB(channel);
  851.  
  852.    if(iob != 0)
  853.    {
  854.        InitBlock(iob, channel);   /* set up for CMD_WRITE */
  855.  
  856.        oct = isave;         /* show the octave we are in */
  857.        wavepointer = wf + woffsets[oct];   /* point to longest wave */
  858.        len = wlen[oct];      /* show its length */
  859.  
  860.           /* Set the parameters */
  861.           iob->ioa_Data = (UBYTE *)wavepointer;
  862.           iob->ioa_Length = len;
  863.           iob->ioa_Period = per;
  864.           iob->ioa_Volume = vol;
  865.  
  866.     iob->ioa_Request.io_Message.mn_Node.ln_Pri = priority;
  867.  
  868.        /* additions for support of tell-me-when-note-starts */
  869.           iob->iob_Identifier = id;
  870.  
  871.     /* see note on same line for PlayNote above */
  872.     iob->ioa_WriteMsg.mn_ReplyPort = messageport;
  873.  
  874.     if(messageport != 0)
  875.        {
  876.               /* If 0, no sending message when note plays;
  877.                 * if nonzero, user gets a message that can
  878.              * be read by MayGetNote.  uport, received from 
  879.          * InitAudio, is what should be the value of
  880.          * messageport. 
  881.          */ 
  882.  
  883.         /* Tell the audio device to "reply" to this message */
  884.         iob->ioa_Request.io_Flags |= ADIOF_WRITEMESSAGE;
  885.         }
  886.  
  887.    /* Calculate cycles from duration in 1000ths of a second */
  888.    /* Multiply all-in-one to maintain max precision possible */
  889.    /* (all integer arithmetic.) */
  890.  
  891.    iob->ioa_Cycles = ((LONG)(freq * duration)/1000);
  892.    BeginIO(iob);
  893.    return(0);      /* all went ok */
  894.    }
  895. return(-1L);    /* (else-part) iob value was zero, out of memory probably */
  896. }
  897.  
  898.  
  899. /* Another new routine called MayGetNote.  Its syntax is:
  900.  *
  901.  *      note = MayGetNote(uport, flag)
  902.  *
  903.  * where uport is the address of the port you received from InitAudio.
  904.  *
  905.  *       flag  is a value of 0 or 1.
  906.  *
  907.  *          when flag = 0, the routine returns immediately, with
  908.  *               a note value of 0 (no note available), or
  909.  *               the value of the first note notification 
  910.  *               to arrive at the port.
  911.  *
  912.  *          when flag = nonzero, the routine will wait if (and only if)
  913.  *               there is no note to be had.  In other words,
  914.  *               you can cause your task to go to sleep until
  915.  *               the next note begins to play.  You decide
  916.  *               what to do for a specific note value.
  917.  *
  918.  *     note  is the value you placed into the PlayNote or PlayFreq
  919.  *               identifier field.  A report of this type
  920.  *               is ONLY generated when "messageport" is nonzero. 
  921.  *   
  922.  *   CAUTION - if there are no more notes with messageport nonzero in
  923.  *         the queue and you specify TRUE for the flag, you can cause your
  924.  *         task to sleep forever!!
  925.  */
  926.  
  927. LONG
  928. MayGetNote(uport, flag)
  929.    struct MsgPort *uport;
  930.    LONG flag;
  931. {
  932.    struct auMsg *aum;
  933.    LONG wakemask;
  934.   
  935. grabmessage:
  936.    aum = (struct auMsg *)GetMsg(uport);   /* is a message there? */
  937.  
  938.    if(aum)   /* There was a message! */ 
  939.    {
  940.       /* The user has seen this msg, so the system can deallocate
  941.        * the iob in which it occurs anytime in the future.
  942.        * Now that we have received the message at our own reply
  943.        * port, it belongs to us and we can do whatever we want
  944.        * to it.  Set the reply port value to zero now, as a signal
  945.        * to FreeIOB that it can really do that! 
  946.        */
  947.       aum->au_Message.mn_ReplyPort = 0;
  948.  
  949.       return(aum->aum_Identifier);  /* return the value */
  950.    }
  951.    if(flag) /* If nonzero, we cause caller to sleep while
  952.              * waiting for any identified iob to appear.
  953.              */
  954.    {
  955.       wakemask = WaitPort(uport);  /* not using wakemask */
  956.       flag = 0;
  957.       goto grabmessage;   /* a goto!  aaarrrggghhh! */
  958.    }
  959. return(0L);   
  960. }
  961.  
  962. /* ReEmployIOB is not new (as compared to article) but it
  963.  * has changed a lot since the article was written.
  964.  */
  965.  
  966. /* ReEmployIOB - look at ALL of the reply ports and if any IOBs
  967.  *        hanging around with nothing to do, free them.
  968.  *
  969.  *       In the article, this routine removed iob's from the
  970.  *       reply ports via the usual GetMsg function.  This
  971.  *       was not practical in this case when adding the support
  972.  *       for audio messages from the device.  
  973.  *
  974.  *  Problem was this:
  975.  *       Audio may still be playing the waveform as we get a
  976.  *       message through MayGetNote.  MayGetNote marks the
  977.  *       iob message block as free-able, (when it finds that
  978.  *       the identifier field is set to zero) but we have to have a
  979.  *       way of recirculating in this list of messages.
  980.  *
  981.  *       In other words, if something is free-able, free it,
  982.  *       otherwise leave it on the list.  So rather than
  983.  *       removing things from the front of the list, lets just
  984.  *       walk through the message list, Remove (dequeue) what is 
  985.  *       freeable and leave the rest there to look at the next time.
  986.  */
  987.  
  988. ReEmployIOB()
  989. {
  990.    LONG i;
  991.    struct MsgPort *mp;
  992.    struct ExtIOB *iob;
  993.  
  994.    /* now declare a pointer to "the first iob pushed back onto the port" */
  995.  
  996.    struct ExtIOB *pushback;    
  997.  
  998.    /* What happens here is that iob's are removed from the message port
  999.     * when they come back from the audio device.   If YOU have set the
  1000.     * messageport nonzero, it means that you wanted to know when
  1001.     * this note began to play.  The WriteMsg part of the iob is then
  1002.     * linked, as a message, onto your user port.  So this routine here
  1003.     * cannot free the iob until it is certain that YOU have finished
  1004.     * using it.  The iob_Priority field is READ here.  If it still
  1005.     * nonzero, the iob is pushed back onto the message port (on the
  1006.     * end of the message queue) to be read again.  We hold a pointer
  1007.     * named pushback that lets us keep track of when we see that
  1008.     * again.  If we see it twice, it means we have completed a full
  1009.     * circle through the queue of messages and have freed everything
  1010.     * that we can this time.  Therefore, we examine it and either
  1011.     * free it or push it back again, then exit.
  1012.     */
  1013.  
  1014.    /* Tiny drawback to the pushback that we do is that there can
  1015.     * be more than one dynamic IOB with the same identifier value.
  1016.     * The id value is not used anywhere, execept for the debug
  1017.     * reporting, so it is not critical.  Note that this is not
  1018.     * a problem with a pushback of a static iob because the inuse[]
  1019.     * array is being maintained for the statics.
  1020.     */
  1021.    for(i=0; i<4; i++)   /* remove all iob's from ALL ports */
  1022.             /* (that is, unless we have to push one back) */
  1023.    {   
  1024.       mp = replyPort[i];
  1025.  
  1026.       pushback = (struct ExtIOB *)0;    /* nothing pushed back so far */
  1027.  
  1028.       while((iob = (struct ExtIOB *)GetMsg(mp)) != 0)
  1029.       { 
  1030.         /* Here is what triggers the Identifier message for MayGetNote */
  1031.  
  1032.         /* First see if messageport in WriteMsg is greater than zero;
  1033.          * if so, audio device is done, but user hasnt acknowledged
  1034.          * this message yet (by using MayGetNote).
  1035.          */
  1036.         if(iob->ioa_WriteMsg.mn_ReplyPort != 0)
  1037.         {
  1038.         /* If we get here, we already know we will push it back */
  1039.         /* because User has not yet freed it (using MayGetNote) */
  1040.  
  1041.         PutMsg(mp, iob);
  1042.  
  1043.         if(iob == pushback) /* If so, we saw it already */
  1044.         {
  1045.             break;       /* Go out to empty next port */
  1046.         }
  1047.         if(pushback == 0)
  1048.         {
  1049.             pushback = iob;    /* Remember FIRST one pushed back */
  1050.         }
  1051.         }
  1052.         else    /* messageport value is zero, can free the iob */
  1053.         {
  1054. #ifdef DEBUG
  1055.             printf("freeing %ls iob; ID=%ld\n",
  1056.                   iob->ioa_Request.io_Message.mn_Node.ln_Name,
  1057.                         iob->ioa_Request.io_Message.mn_Length);
  1058. #endif DEBUG
  1059.                 FreeIOB(iob,i);
  1060.         }
  1061.       }
  1062.  
  1063.    }
  1064.    return(0);
  1065. }
  1066.  
  1067. /* Use this next function to determine if you (still) own a particular channel.
  1068.  * The audio device has an arrangement by which a higher priority request
  1069.  * for a channel than the one that already owns it can be made.  The higher
  1070.  * priority request can actually cause a channel to be stolen from a user.
  1071.  * This feature may be implemented in a future version of audiotools,
  1072.  * (shared library version), in which, depending on the task's running
  1073.  * priority itself, a higher priority task could succeed at GetChannel
  1074.  * for a channel that is already owned by another task.
  1075.  */
  1076.  
  1077. int
  1078. IsThatMyChan(channel)    /* returns nonzero if YOU still own the channel */
  1079.     LONG channel;
  1080. {
  1081.     if(channel < 0 || channel > 3)         return(0);
  1082.     if(usertask[channel] == FindTask(0))     return(TRUE);
  1083.     return(0);
  1084. }
  1085.  
  1086.  
  1087.  
  1088. /* HERE BEGINS NEW STUFF, ADDED FOR RELEASE 3.0 */
  1089.  
  1090.  
  1091. /* PlaySamp - play a sampled sound:
  1092.  *
  1093.  *    identical to PlayFreq but the parameters are interpreted differently.
  1094.  *    "freq" now becomes "period" interpreted as sampling_rate, 
  1095.  *                must be in the range of 127 to 500.
  1096.  *    "duration" still is expressed in 1000ths of a second to play it.
  1097.  *    (as with the audio device itself, a duration of 0 means do it forever
  1098.  *    or until the audio device is reset or the channel is flushed or
  1099.  *    until this command is explicitly aborted.)
  1100.  *    "length" is the number of bytes in the waveform.  It is a NEW parameter
  1101.  *    for this command only.
  1102.  */
  1103. int 
  1104. PlaySamp(channel, period, wf, vol, duration, priority, messageport, id, length)
  1105.    char *wf;   /* waveform to use */
  1106.    LONG vol, channel, period;   /* specific integer frequency */
  1107.    LONG priority;
  1108.    LONG duration;
  1109.    struct MsgPort *messageport;
  1110.    LONG id, length;
  1111. {
  1112.    LONG per, len;   /* period, length of waveform, which octave */
  1113.    char *wavepointer;   /* where to find start of waveform */
  1114.    struct ExtIOB *iob;
  1115.    long cycles, ipart;
  1116.    long dividend, divisor;
  1117.  
  1118.    if(channel < 0 || channel > 3)    return(BAD_CHANNEL_SELECTED);
  1119. printf("channel is ok\n");
  1120.    if(usertask[channel] != FindTask(0)) return(NOT_YOUR_CHANNEL);
  1121. printf("task is ok\n");
  1122.    if(priority < -128 || priority > 127)return(OUT_OF_RANGE_PRI);
  1123. printf("priority is ok\n");
  1124.    per = period;
  1125.  
  1126.    if(per > 500 || per < 127) return(OUT_OF_RANGE_FREQ);
  1127. printf("period is ok\n");
  1128.    iob = GetIOB(channel);
  1129.  
  1130.    if(iob != 0)
  1131.    {
  1132.        InitBlock(iob, channel);   /* set up for CMD_WRITE */
  1133.  
  1134.        wavepointer = wf;
  1135.        len = length;           /* show its length;
  1136.  
  1137. printf("length is set\n");
  1138.  
  1139.           /* Set the parameters */
  1140.           iob->ioa_Data = (UBYTE *)wavepointer;
  1141.           iob->ioa_Length = len;
  1142.           iob->ioa_Period = per;
  1143.           iob->ioa_Volume = vol;
  1144. printf("parameters are set\n");
  1145.  
  1146.     iob->ioa_Request.io_Message.mn_Node.ln_Pri = priority;
  1147. printf("priority is set\n");
  1148.  
  1149.        /* additions for support of tell-me-when-note-starts */
  1150.           iob->iob_Identifier = id;
  1151. printf("id is set\n");
  1152.  
  1153.     /* see note on same line for PlayNote above */
  1154.     iob->ioa_WriteMsg.mn_ReplyPort = messageport;
  1155.  
  1156. printf("msgport is ok\n");
  1157.  
  1158.     if(messageport != 0)
  1159.        {
  1160.               /* If 0, no sending message when note plays;
  1161.                 * if nonzero, user gets a message that can
  1162.              * be read by MayGetNote.  uport, received from 
  1163.          * InitAudio, is what should be the value of
  1164.          * messageport. 
  1165.          */ 
  1166.  
  1167.         /* Tell the audio device to "reply" to this message */
  1168.         iob->ioa_Request.io_Flags |= ADIOF_WRITEMESSAGE;
  1169.         }
  1170.  
  1171.    /* Calculate cycles from duration in 1000ths of a second */
  1172.    /* Multiply all-in-one to maintain max precision possible */
  1173.    /* (all integer arithmetic.) */
  1174.  
  1175.    ipart = 0;
  1176.  
  1177.    if(duration > 1000)
  1178.    {
  1179.     ipart = duration/1000;
  1180.  
  1181.         duration = duration - (ipart * 1000);
  1182.    }
  1183.  
  1184.    /* fool it a little so we dont get integer overflow...
  1185.     * 3.5 million times 1000 is about all we can take in a 32 bit word
  1186.     */
  1187.    dividend = (3579545 * ipart) + ((long)3579545 * (long)duration)/(long)1000 ;
  1188. printf("dividend is %ld\n",dividend);
  1189.  
  1190.    divisor  = len * per;
  1191. printf("divisor is %ld\n", divisor);
  1192.  
  1193.    cycles   = (long)dividend / (long)divisor;
  1194. printf("cycles calculated to be %ld\n", cycles);
  1195.  
  1196.    iob->ioa_Cycles = cycles;
  1197.  
  1198.    if(duration != 0 && iob->ioa_Cycles == 0) iob->ioa_Cycles = 1;
  1199. printf("cycles = %ld\n", iob->ioa_Cycles);
  1200.  
  1201.    BeginIO(iob);
  1202.    return(0);      /* all went ok */
  1203.    }
  1204. return(-1L);    /* (else-part) iob value was zero, out of memory probably */
  1205. }
  1206.